<!--
 * @Description  : 
 * @Version      : 1.0
 * @Author       : seraph
 * @Date         : 2024-12-26 12:19:10
 * @LastEditors  : seraph
 * @LastEditTime : 2025-01-20 11:38:09
-->
<template>
  <h3>计算属性缓存 vs 方法</h3>
  <p>你可能注意到我们在表达式中像这样调用一个函数也会获得和计算属性相同的结果:</p>
  <pre><code class="javascript" v-html="highlighted1"></code></pre>
  <p>
    若我们将同样的函数定义为一个方法而不是计算属性，两种方式在结果上确实是完全相同的，然而，不同之处在于计算属性值会基于其响应式依赖被缓存。<span
      class="red">一个计算属性仅会在其响应式依赖更新时才重新计算</span>。这意味着只要 author.books 不改变，无论多少次访问 publishedBooksMessage
    都会立即返回先前的计算结果，而不用重复执行 getter 函数。
  </p>
  <p>这也解释了为什么下面的计算属性永远不会更新，因为 Date.now() 并不是一个响应式依赖：</p>
  <pre><code class="javascript" v-html="highlighted2"></code></pre>
  <p>第一次调用 now：{{ now }}，连续调用 now：{{ tmpStr }}</p>
  <button @click="outputNow">调用输出 now</button>
  <p>相比之下，方法调用总是会在重渲染发生时再次执行函数。</p>
  <p>
    为什么需要缓存呢？想象一下我们有一个非常耗性能的计算属性
    list，需要循环一个巨大的数组并做许多计算逻辑，并且可能也有其他计算属性依赖于
    list。没有缓存的话，我们会重复执行非常多次 list 的
    getter，然而这实际上没有必要！如果你确定不需要缓存，那么也可以使用方法调用。
  </p>
</template>

<script lang="ts" setup>
import { ref, reactive, nextTick, shallowRef, watch, computed } from 'vue'
import hljs from 'highlight.js'
import javascript from 'highlight.js/lib/languages/javascript'
import html from 'highlight.js/lib/languages/xml'
import 'highlight.js/styles/atom-one-light.css'

hljs.registerLanguage('javascript', javascript)
hljs.registerLanguage('xml', html)

const rawCode1 = `// 在模板中
<p>{{ calculateBooksMessage() }}</p>
// 组件中
function calculateBooksMessage() {
return author.books.length > 0 ? 'Yes' : 'No'
}
`
const highlighted1 = ref(
  hljs
    .highlight(rawCode1, {
      language: 'javascript',
    })
    .value.replace(/&amp;gt;/g, '>')
    .replace(/&amp;lt;/g, '<'),
)

const rawCode2 = `const now = computed(() =&gt; Date.now())`

const highlighted2 = ref(
  hljs
    .highlight(rawCode2, {
      language: 'javascript',
    })
    .value.replace(/&amp;lt;/g, '&lt;')
    .replace(/&amp;gt;/g, '&gt;'),
)

const author = reactive({
  name: 'John Doe',
  firstName: 'John',
  lastName: 'Doe',
  books: ['Vue 2 - Advanced Guide', 'Vue 3 - Basic Guide', 'Vue 4 - The Mystery'],
})

const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? 'Yes' : 'No'
})

const now = computed(() => Date.now())

const tmpStr = ref('')
const outputNow = () => {
  tmpStr.value = Date.now().toString().slice(-3) + ':' + now.value
  console.log(tmpStr.value)
}
</script>

<style scoped>
p {
  font-size: 16px;
  padding: 2px;
  margin: 5px 0;
}

/* 设置预格式化文本的样式 */
pre {
  /* 背景色 */
  background-color: #f1ffed;
  /* 边框 */
  border: 1px solid #ccc;
  /* 缩小内边距 */
  padding: 5px 5px 5px 10px;
  /* 允许滚动 */
  overflow: auto;
  /* 可选：设置上下外边距 */
  margin: 0px 0;
  /* 允许换行 */
  /* normal: 连续的空白字符会被合并为一个空格。文本会在允许的断点处自动换行。
     nowrap：连续的空白字符会被合并为一个空格。文本不会自动换行，即使在允许的断点处也不会换行。
     pre：保留所有的空白字符（包括空格、制表符和换行符）。文本只会在遇到换行符或者 <br> 标签时换行。
     pre-wrap：保留所有的空白字符。文本会在遇到换行符、<br> 标签，或者达到容器边界时换行。
     pre-line：合并连续的空白字符为一个空格，但保留换行符。文本会在遇到换行符、<br> 标签，或者达到容器边界时换行。
   */
  white-space: pre-wrap;
  /* 行高 */
  line-height: 120%;
  /* 定义缩进 */
  tab-size: 4;
}

h3,
h4,
h5,
h6 {
  margin: 25px 0 10px 0;
  line-height: 100%;
}

.bolder {
  font-weight: bolder;
}

.red {
  color: red;
}

button {
  margin: 5px 5px 5px 10px;
  line-height: 150%;
  width: auto;
  padding: 0 10px;
}
</style>
